package cn.jilian.sys.service.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.transaction.Transactional;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;

import cn.jilian.dao.MaceDAO;
import cn.jilian.dao.MenuDAO;
import cn.jilian.dao.UserDAO;
import cn.jilian.entity.Mace;
import cn.jilian.entity.Menu;
import cn.jilian.entity.MenuAct;
import cn.jilian.entity.MenuGroup;
import cn.jilian.entity.MenuPage;
import cn.jilian.entity.Role;
import cn.jilian.entity.User;
import cn.jilian.sys.service.MenuService;
import cn.jilian.sys.service.support.MenuLoader;
import cn.jilian.sys.service.support.loaders.SysMenuLoader;


@Service
public class MenuServiceImpl implements MenuService {

	private Logger logger=LoggerFactory.getLogger(getClass());
	
	@Autowired
	private MenuDAO menuDAO;
	@Autowired
	private MaceDAO maceDAO;
	@Autowired
	private UserDAO userDAO;
	@Autowired
	private ObjectMapper objectMapper;
	
	
	private Map<Integer,MenuLoader> menuLoaderMap=new ConcurrentHashMap<>();
	
	private MenuLoader getMenuLoader(Integer rankId) {
		MenuLoader loader=menuLoaderMap.get(rankId);
		if(loader==null) {
			loader=new SysMenuLoader(menuDAO, rankId);
			menuLoaderMap.put(rankId, loader);
		}
		return loader;
	}
	
	@Override
	@Transactional
	public Menu saveMenu(Menu menu) {
		MenuLoader loader=getMenuLoader(menu.getRank());
		Menu realMenu=objectMapper.convertValue(menu, Menu.MetaClassMap.get(menu.getMeta()));
		loader.updateMenu(realMenu);
		return menu;
	}
	@Override
	@Transactional
	public void delete(long id) {
		Menu m =menuDAO.findOne(id);
		if(m!=null) {
			MenuLoader loader=getMenuLoader(m.getRank());
			loader.delete(id);
			maceDAO.deleteByFid(id);
			
		}
		
	}
	@Override
	@Transactional
	public List<Menu> loadUserMenu(Long userId,Integer rankId) {
		User user=userDAO.findOne(userId);
		return loadUserMenu(user,rankId);
	}
	private Set<Long> toFids(List<Mace> maces){
		Set<Long> rs=new HashSet<>();
		for(Mace m:maces) {
			Long fid=m.getFid();
			if(fid!=null&&m.getAct().compareTo(0)>0) {
				rs.add(fid);
			}
		}
		return rs;
	}
	@Override
	@Transactional
	public List<Menu> loadUserMenu(User user,Integer rankId) {
		MenuLoader loader=getMenuLoader(rankId);
		Collection<Long> ids=getAllMaceUids(user);
		List<Mace> maces=maceDAO.findByUidInAndRank(ids, Mace.MENU_MACE_RANK);
		if(CollectionUtils.isEmpty(maces))
			return Collections.emptyList();
		Set<Long> fids=toFids(maces);
		List<Menu> menus=loader.getAllTree();
		List<Menu> rs=new ArrayList<>();
		try {
		for(Menu m:menus) {
			if(fids.contains(m.getId())) {
				Menu cm=(Menu) BeanUtils.cloneBean(m);
				rs.add(cm);
				if(MenuGroup.class.isAssignableFrom(cm.getClass())) {
					MenuGroup mg=(MenuGroup)cm;
					List<Menu> cs=mg.getChildren();
					if(CollectionUtils.isNotEmpty(cs)) {
						List<Menu> rcs=filterMenus(cs,fids);
						mg.setChildren(rcs);
					}
				}else if(MenuPage.class.isAssignableFrom(cm.getClass())) {
					MenuPage mp=(MenuPage) cm;
					mp.setChildren(null);
					mp.setLeaf(Boolean.TRUE);
				}
			}
		}
		}catch(Exception e) {
			
		}
		
		return rs;
	}
	
	private List<Menu> filterMenus(List<Menu> list,Set<Long> fids){
		List<Menu> rs=new ArrayList<>();
		try {
		for(Menu m:list) {
			
			if(fids.contains(m.getId())){
				Menu x=m;
				if(MenuPage.class.isAssignableFrom(m.getClass())) {
					MenuPage mp=(MenuPage)m;
					if(CollectionUtils.isNotEmpty(mp.getChildren())) {
						MenuPage cm=(MenuPage) BeanUtils.cloneBean(m);
						cm.setChildren(null);
						cm.setLeaf(Boolean.TRUE);
						x=cm;
					}
				}
				rs.add(x);
			}
		}
		}catch(Exception e) {
			e.printStackTrace();
		}
		return rs;
	}
	
	private Collection<Long> getAllMaceUids(User user){
		Collection<Long> ids=new HashSet<>();
		Set<Role> roles=user.getRoles();
		if(CollectionUtils.isNotEmpty(roles)) {
			for(Role role:roles) {
				ids.add(role.getId());
			}
		}
		ids.add(user.getId());
		if(user.getMeta()!=null) {
			ids.add(user.getMeta());
		}
		return ids;
	}
	@Override
	public List<Menu> getAllMenuTree(int rankId) {
		MenuLoader loader=getMenuLoader(rankId);
		return loader.getAllTree();
	}
	@Override
	@Transactional
	public Menu addMenu(Menu menu) {
		MenuLoader loader=getMenuLoader(menu.getRank());
		Menu realMenu=objectMapper.convertValue(menu, Menu.MetaClassMap.get(menu.getMeta()));
		loader.addMenu(realMenu);
		return menu;
	}
	@Override
	@Transactional
	public int updateMenuLoc(long sid, long fid, int seq) {
		Menu menu=menuDAO.findOne(sid);
		Integer rankId=menu.getRank();
		MenuLoader loader=getMenuLoader(rankId);
		Integer oldSeq=menu.getSeq();
		Integer max1=menuDAO.getMaxSeq(menu.getFid(), Menu.RANK);
		if(menu.getFid().equals(fid)) {
			if(oldSeq!=null&&!oldSeq.equals(seq)) {
				if(oldSeq.compareTo(seq)>0) {
					menuDAO.offsetSeq(fid, seq, oldSeq-1, 1);
				}else {
					menuDAO.offsetSeq(fid, oldSeq, seq, -1);
				}
				menu.setSeq(seq);
				menuDAO.save(menu);
				loader.refresh();
			}
		}else {
			if(!oldSeq.equals(max1)) {
				menuDAO.offsetSeq(menu.getFid(), oldSeq, max1, -1);
			}
			Integer max2=menuDAO.getMaxSeq(fid, Menu.RANK);
			if(max2.compareTo(seq)>0) {
				menuDAO.offsetSeq(fid, seq, max2, 1);
			}
			menu.setFid(fid);
			menu.setSeq(seq);
			menuDAO.save(menu);
			loader.refresh();
		}
		
		
		return 1;
	}
	@Override
	public List<Menu> getChildren(Long id,Integer rankId) {
		MenuLoader loader=getMenuLoader(rankId);
		return loader.getChildren(id);
	}
	
	@Override
	public List<Menu> getChildrenWithActions(Long id, Integer rankId) {
		MenuLoader loader=getMenuLoader(rankId);
		return loader.getChildren(id);
	}

	private void collectPerms(List<Menu> cs,Map<Long,Integer> maceMap,Set<String> maces) {
		if(CollectionUtils.isNotEmpty(cs)) {
			for(Menu c:cs) {
				if(MenuAct.class.isAssignableFrom(c.getClass())) {
					MenuAct menuAct=(MenuAct)c;
					Integer act=maceMap.get(c.getFid());
					if(act!=null) {
						Integer mact=menuAct.getAct();
						if(mact.intValue()==(mact.intValue()&act.intValue())) {
							if(StringUtils.isNotBlank(menuAct.getName())) {
								maces.add(menuAct.getName());
							}else {
							}
						}
					}
				}
				
				
			}
		}
	}
	@Override
	public Set<String> getPerms(User user, Integer rankId) {
		MenuLoader loader=getMenuLoader(rankId);
		Set<Role> roles=user.getRoles();
		List<Mace> userMaces=maceDAO.findByUidInAndRank(Lists.newArrayList(user.getId(),user.getMeta()), Mace.MENU_MACE_RANK);
		Set<String> maces=new HashSet<>();
		Map<Long,Integer> maceMap=new HashMap<>();
		if(CollectionUtils.isNotEmpty(userMaces)) {
			for(Mace userMace:userMaces) {
				Integer oldact=maceMap.get(userMace.getFid());
				if(oldact==null) {
					maceMap.put(userMace.getFid(), userMace.getAct());
				}else {
					maceMap.put(userMace.getFid(),oldact.intValue()|userMace.getAct().intValue());
				}
				
			}
		}
		if(CollectionUtils.isNotEmpty(roles)) {
			for(Role r:roles) {
				Map<Long,Integer> ms=r.getMenuMaceMap();
				for(Map.Entry<Long, Integer> e:ms.entrySet() ) {
					
					Integer acts=e.getValue();
					Integer act=maceMap.get(e.getKey());
					if(act==null) {
						maceMap.put(e.getKey(), acts);
					}else {
						act=acts|act;
						maceMap.put(e.getKey(), act);
					}
				}
			}
		}
		if(logger.isDebugEnabled()) {
			try {
			logger.debug("user[id={}]'s maceMap=[{}]",user.getId(),objectMapper
					.writerWithDefaultPrettyPrinter().writeValueAsString(maceMap));
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		for(Map.Entry<Long, Integer> e:maceMap.entrySet()) {
			Menu m=loader.getMenu(e.getKey());
			if(m!=null) {
				
				if(MenuPage.class.isAssignableFrom(m.getClass())) {
					MenuPage mp=(MenuPage)m;
					Integer mact=maceMap.get(mp.getId());
					if(mact!=null) {
						Integer ac=mp.getAct();
						if(ac!=null&&ac==(mact&ac)) {
							String name=mp.getName();
							if(StringUtils.isNotBlank(name))
							maces.add(name);
						}
						
					}
					List<Menu> cs=mp.getChildren();
					collectPerms(cs,maceMap,maces);
				}
			}
		}
		if(logger.isDebugEnabled()) {
			try {
			logger.debug("user[id={}]'s maceSets=[{}]",user.getId(),objectMapper
					.writerWithDefaultPrettyPrinter().writeValueAsString(maces));
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		return maces;
	}

	@Override
	public Menu getMenu(Long id) {
		
		return menuDAO.findOne(id);
	}

	

}
